package com.anonymous.util.test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;

import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;

import com.anonymous.message.ChatMsg;
import com.anonymous.message.HelloMsg;
import com.anonymous.message.Message;
import com.anonymous.message.X509CertificateMsg;
import com.anonymous.util.ConfidentialitySecurityUtils;
import com.anonymous.util.NetworkUtils;

public class SSLClient {

	/**
	 * @param args
	 */
	/**
	 * The server port.
	 */
	private static int port = 6677;

	/**
	 * KeyStore for storing our public/private key pair
	 */
	private KeyStore clientKeyStore;

	/**
	 * KeyStore for storing the server's public key
	 */
	private KeyStore serverKeyStore;

	/**
	 * Used to generate a SocketFactory
	 */
	private static SSLContext sslContext;

	/**
	 * Passphrase for accessing our authentication keystore
	 */
	static private final String passphrase = "clientpw";

	/**
	 * A source of secure random numbers
	 */
	static private SecureRandom secureRandom;

	/**
	 * Connection to the client
	 */
	private static DataInputStream din;

	/**
	 * Connection to the client
	 */
	private static DataOutputStream dout;

	private ConfidentialitySecurityUtils confidentialitySecurityUtils;
	private KeyPair clientKeyPair;
	private X509Certificate clientCertificate;
	private NetworkUtils networkUtils;

	public static void main(String[] args) throws NoSuchPaddingException,
			Exception {
		secureRandom = new SecureRandom();
		secureRandom.nextInt();
		new SSLClient("127.0.0.1");
		System.out.println("done");
		// X509CertificateHolder cert = generateSelfSignedX509Certificate();

	}

	private void connect(String host) throws Exception {
		try {
			setupServerKeystore();
			setupClientKeyStore();
			setupSSLContext();
			/*
			// send its key to server
			Socket clientNormalSocket = new Socket(host, port);
			networkUtils.send(new X509CertificateMsg(clientCertificate),
					clientNormalSocket);
			
			Message result = (Message) networkUtils.receive(clientNormalSocket);
			if (!result.getFlag().equals("CERTIFICATE_RECEIVED"))
				throw new Exception();
			clientNormalSocket.close();
			*/
			Message msg;
			SSLSocketFactory sf = sslContext.getSocketFactory();
			SSLSocket clientSocket = (SSLSocket) sf
					.createSocket(host, port);
			new SSLSend(clientSocket).start();
			while (true) {
				msg = networkUtils.receive(clientSocket);
				if(!msg.getFlag().equals("CHAT_MSG"))return;
				System.out.println(((ChatMsg)msg).getMessage()+ ": From " + clientSocket.getRemoteSocketAddress());

				// ConnectionProcessor cp = new ConnectionProcessor(this,
				// socket);
			}
		} catch (GeneralSecurityException gse) {
			gse.printStackTrace();
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

	public SSLClient(String host) throws Exception, NoSuchPaddingException {

		confidentialitySecurityUtils = new ConfidentialitySecurityUtils();
		networkUtils = new NetworkUtils();
		connect(host);
	}

	private void setupServerKeystore() throws GeneralSecurityException,
			IOException {
		serverKeyStore = KeyStore.getInstance("JKS");

		serverKeyStore.load(new FileInputStream("server.public"),
				"public".toCharArray());
	}

	private void setupClientKeyStore() throws Exception {
		clientKeyStore = KeyStore.getInstance("JKS");
		// GENERATE AN EMPTY clientKeyStore
		clientKeyStore.load(null, passphrase.toCharArray());
		clientKeyStore = null;
		/*
		clientKeyPair = confidentialitySecurityUtils.generateCustomKeyPair();
		X509Certificate clientCert[] = new X509Certificate[1];
		clientCertificate = generateSelfSignedX509Certificate(clientKeyPair);
		clientCert[0] = clientCertificate;
		KeyStore.PrivateKeyEntry clientPrivateKeyEntry = new KeyStore.PrivateKeyEntry(
				clientKeyPair.getPrivate(), clientCert);
		clientKeyStore.setEntry("clientPrivateKey", clientPrivateKeyEntry,
				new KeyStore.PasswordProtection(passphrase.toCharArray()));

		// Add the certificate
		clientKeyStore.setCertificateEntry("clientCertificate", clientCert[0]);*/
	}

	private void setupSSLContext() throws GeneralSecurityException, IOException {
		TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
		tmf.init(serverKeyStore);

		KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
		kmf.init(clientKeyStore, passphrase.toCharArray());

		sslContext = SSLContext.getInstance("TLS");
		sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
				secureRandom);
	}

	private static X509Certificate generateSelfSignedX509Certificate(
			KeyPair keyPair) throws Exception {

		// yesterday
		Date validityBeginDate = new Date(System.currentTimeMillis() - 24 * 60
				* 60 * 1000);
		// in 2 years
		Date validityEndDate = new Date(System.currentTimeMillis() + 2 * 365
				* 24 * 60 * 60 * 1000);

		// GENERATE THE PUBLIC/PRIVATE RSA KEY PAIR

		// GENERATE THE X509 CERTIFICATE
		SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(
				(ASN1Sequence) ASN1Object.fromByteArray(keyPair.getPublic()
						.getEncoded()));
		X500Name dnName = new X500Name("CN=John Doe");

		X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
				dnName, BigInteger.valueOf(System.currentTimeMillis()),
				validityBeginDate, validityEndDate, dnName,
				subjectPublicKeyInfo);

		certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false,
				createSubjectKeyId(keyPair.getPublic()));

		// prepare the signer with the private Key
		AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
				.find("SHA256withRSA");
		AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder()
				.find(sigAlgId);

		BcRSAContentSignerBuilder sigBuild = new BcRSAContentSignerBuilder(
				sigAlgId, digAlgId);

		// hopefully format is PKCS#8
		AsymmetricKeyParameter foo = PrivateKeyFactory.createKey(keyPair
				.getPrivate().getEncoded());

		ContentSigner sigGen = sigBuild.build(foo);

		X509CertificateHolder certHolder = certBuilder.build(sigGen);
		X509CertificateStructure eeX509CertificateStructure = certHolder
				.toASN1Structure();

		// retrieve the certificate from holder

		CertificateFactory cf = CertificateFactory.getInstance("X.509");

		// Read Certificate
		InputStream is1 = new ByteArrayInputStream(
				eeX509CertificateStructure.getEncoded());
		X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);
		is1.close();

		return theCert;
	}

	//
	// create the subject key identifier.
	//
	public static SubjectKeyIdentifier createSubjectKeyId(PublicKey pubKey) {
		try {
			ByteArrayInputStream bIn = new ByteArrayInputStream(
					pubKey.getEncoded());
			SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
					(ASN1Sequence) new ASN1InputStream(bIn).readObject());

			return new SubjectKeyIdentifier(info);
		} catch (Exception e) {
			throw new RuntimeException("ERROR: Creating Subject Key ID");
		}
	}

}
